import React, { useMemo, useState } from 'react'
import { ConfigProvider, App as AntApp, Layout, Typography, Space, Upload, message, Alert, Button, Table, Tag, Select, Input, Divider, Flex, Modal, Tooltip } from 'antd'
import { InboxOutlined, DownloadOutlined, SettingOutlined } from '@ant-design/icons'
import * as XLSX from 'xlsx'
import dayjs from 'dayjs'
import { z } from 'zod'

const { Header, Content } = Layout
const { Title, Text } = Typography

// Types
export type IssueRow = Record<string, any>

export type IssueType = '咨询' | '需求' | 'BUG'
export type IssueStatus = '处理中' | '已接纳' | '已解决' | '待验证' | '不接纳'

export type TagLabel = '正常' | '异常' | '待定'

export interface FieldMapping {
  issueId: string
  type: string
  status: string
  createdAt: string
  updatedAt: string
  demandId?: string
  dtsId?: string
}

export interface RefMapping {
  id: string
  closedFlag: string
  closedTrueValues?: string // comma-separated values meaning closed
}

interface LoadedTables {
  issues?: { rows: IssueRow[]; headers: string[]; mapping?: FieldMapping }
  demand?: { rows: IssueRow[]; headers: string[]; mapping?: RefMapping }
  dts?: { rows: IssueRow[]; headers: string[]; mapping?: RefMapping }
}

interface LabeledIssue extends IssueRow {
  __label__: TagLabel
  __reason__?: string
}

// Helpers
function parseWorkbook(file: File): Promise<{ rows: IssueRow[]; headers: string[] } | null> {
  return new Promise((resolve, reject) => {
    const reader = new FileReader()
    reader.onload = (e) => {
      try {
        const data = new Uint8Array(e.target?.result as ArrayBuffer)
        const wb = XLSX.read(data, { type: 'array', cellDates: true, raw: false })
        const sheetName = wb.SheetNames[0]
        const ws = wb.Sheets[sheetName]

        // Read rows as 2D array first to robustly detect the header row
        const rows2d = XLSX.utils.sheet_to_json(ws, { header: 1, defval: '' }) as any[][]
        const headerIndex = Math.max(0, rows2d.findIndex((r) => Array.isArray(r) && r.some((c) => String(c).trim().length > 0)))
        const headers = (rows2d[headerIndex] || []).map((h) => String(h || '').trim())
        const dataRows = rows2d.slice(headerIndex + 1)
        const rows: IssueRow[] = dataRows.map((arr) => {
          const obj: IssueRow = {}
          headers.forEach((h, idx) => {
            obj[h] = arr[idx] ?? ''
          })
          return obj
        })

        resolve({ rows, headers })
      } catch (err) {
        reject(err)
      }
    }
    reader.onerror = reject
    reader.readAsArrayBuffer(file)
  })
}

function normalizeHeaderMatch(headers: string[], candidates: string[]): string | undefined {
  const set = new Set(headers.map((h) => String(h).trim()))
  for (const c of candidates) {
    if (set.has(c)) return c
  }
  return undefined
}

function buildDefaultIssueMapping(headers: string[]): FieldMapping | undefined {
  const issueId = normalizeHeaderMatch(headers, ['ID号'])
  const type = normalizeHeaderMatch(headers, ['类型'])
  const status = normalizeHeaderMatch(headers, ['状态'])
  const createdAt = normalizeHeaderMatch(headers, ['创建时间(UTC)'])
  const updatedAt = normalizeHeaderMatch(headers, ['最近更新时间(UTC)'])
  const demandId = normalizeHeaderMatch(headers, ['关联单号'])
  const dtsId = normalizeHeaderMatch(headers, ['关联单号'])
  if (!issueId || !type || !status || !createdAt || !updatedAt) return undefined
  return { issueId, type, status, createdAt, updatedAt, demandId, dtsId }
}

function buildDefaultRefMapping(headers: string[]): RefMapping | undefined {
  const id = normalizeHeaderMatch(headers, ['需求单号', 'DTS单号', '单号', '编号', 'ID', '缺陷单号'])
  const closedFlag = normalizeHeaderMatch(headers, ['是否关闭', '关闭', '状态', '当前状态', '单据状态', '是否已关闭', '是否结束'])
  if (!id || !closedFlag) return undefined
  return { id, closedFlag, closedTrueValues: '已关闭,是,true,1' }
}

function safeParseDate(value: any): dayjs.Dayjs | null {
  if (!value && value !== 0) return null
  // If Excel provided a JS Date object or an ISO string
  if (value instanceof Date) {
    const d = dayjs(value)
    return d.isValid() ? d : null
  }
  // If numeric Excel date serial
  if (typeof value === 'number') {
    const jsDate = new Date((value - 25569) * 86400 * 1000)
    const d = dayjs(jsDate)
    return d.isValid() ? d : null
  }
  const d = dayjs(String(value))
  return d.isValid() ? d : null
}

function isClosed(value: any, mapping: RefMapping): boolean {
  const raw = String(value).trim()
  const truths = (mapping.closedTrueValues || '已关闭,是,true,1')
    .split(',')
    .map((s) => s.trim())
    .filter(Boolean)
  return truths.includes(raw)
}

function ceilDaysOver(elapsedDays: number, thresholdDays: number): number {
  const over = Math.ceil(elapsedDays - thresholdDays)
  return over > 0 ? over : 0
}

function labelIssues(
  issues: IssueRow[],
  mapping: FieldMapping,
  opts: {
    demand?: { rows: IssueRow[]; mapping: RefMapping }
    dts?: { rows: IssueRow[]; mapping: RefMapping }
  }
): LabeledIssue[] {
  const demandMap = new Map<string, IssueRow>()
  const dtsMap = new Map<string, IssueRow>()

  if (opts.demand) {
    for (const r of opts.demand.rows) {
      demandMap.set(String(r[opts.demand.mapping.id]).trim(), r)
    }
  }
  if (opts.dts) {
    for (const r of opts.dts.rows) {
      dtsMap.set(String(r[opts.dts.mapping.id]).trim(), r)
    }
  }

  const now = dayjs()

  const labeled: LabeledIssue[] = issues.map((row) => {
    const type = String(row[mapping.type]).trim() as IssueType
    const status = String(row[mapping.status]).trim() as IssueStatus

    const createdAt = safeParseDate(row[mapping.createdAt])
    const updatedAt = safeParseDate(row[mapping.updatedAt])

    let label: TagLabel = '正常'
    let reason: string | undefined

    const elapsedProcessing = createdAt ? now.diff(createdAt, 'day', true) : 0
    const elapsedVerify = updatedAt ? now.diff(updatedAt, 'day', true) : 0

    const processingOver = ceilDaysOver(elapsedProcessing, 7)
    const verifyOver = ceilDaysOver(elapsedVerify, 7)

    const ensureRefAvailable = (needed: 'demand' | 'dts'): boolean => {
      if (needed === 'demand') return Boolean(opts.demand)
      return Boolean(opts.dts)
    }

    if (type === '咨询') {
      if (status === '已解决') {
        label = '正常'
      } else if (status === '已接纳') {
        label = '异常'; reason = '状态不允许: 已接纳'
      } else if (status === '不接纳') {
        label = '异常'; reason = '状态不允许: 不接纳'
      } else if (status === '处理中') {
        if (processingOver > 0) { label = '异常'; reason = `处理中超时>${processingOver}天` } else { label = '正常' }
      } else if (status === '待验证') {
        if (verifyOver > 0) { label = '异常'; reason = `待验证超时>${verifyOver}天` } else { label = '正常' }
      }
    } else if (type === '需求') {
      if (status === '已接纳') {
        if (!ensureRefAvailable('demand')) {
          label = '待定'; reason = '缺少需求表，请上传以完成打标'
        } else {
          const demandIdKey = mapping.demandId
          if (!demandIdKey || !row[demandIdKey]) {
            label = '异常'; reason = '已接纳但无需求单号'
          } else {
            const key = String(row[demandIdKey]).trim()
            const ref = demandMap.get(key)
            if (!ref) {
              label = '异常'; reason = '已接纳但需求单不存在/未找到'
            } else {
              const closed = isClosed(ref[opts.demand!.mapping.closedFlag], opts.demand!.mapping)
              if (closed) { label = '异常'; reason = '已接纳但需求单已关闭' } else { label = '正常' }
            }
          }
        }
      } else if (status === '不接纳' || status === '已解决') {
        label = '正常'
      } else if (status === '处理中') {
        if (processingOver > 0) { label = '异常'; reason = `处理中超时>${processingOver}天` } else { label = '正常' }
      } else if (status === '待验证') {
        if (verifyOver > 0) { label = '异常'; reason = `待验证超时>${verifyOver}天` } else { label = '正常' }
      }
    } else if (type === 'BUG') {
      if (status === '已接纳') {
        if (!ensureRefAvailable('dts')) {
          label = '待定'; reason = '缺少DTS表，请上传以完成打标'
        } else {
          const dtsIdKey = mapping.dtsId
          if (!dtsIdKey || !row[dtsIdKey]) {
            label = '异常'; reason = '已接纳但无DTS单号'
          } else {
            const key = String(row[dtsIdKey]).trim()
            const ref = dtsMap.get(key)
            if (!ref) {
              label = '异常'; reason = '已接纳但DTS单不存在/未找到'
            } else {
              const closed = isClosed(ref[opts.dts!.mapping.closedFlag], opts.dts!.mapping)
              if (closed) { label = '异常'; reason = '已接纳但DTS单已关闭' } else { label = '正常' }
            }
          }
        }
      } else if (status === '不接纳' || status === '已解决') {
        label = '正常'
      } else if (status === '处理中') {
        if (processingOver > 0) { label = '异常'; reason = `处理中超时>${processingOver}天` } else { label = '正常' }
      } else if (status === '待验证') {
        if (verifyOver > 0) { label = '异常'; reason = `待验证超时>${verifyOver}天` } else { label = '正常' }
      }
    }

    return { ...row, __label__: label, __reason__: reason }
  })

  return labeled
}

function exportToExcel(rows: IssueRow[], filename: string) {
  const ws = XLSX.utils.json_to_sheet(rows)
  const wb = XLSX.utils.book_new()
  XLSX.utils.book_append_sheet(wb, ws, 'Sheet1')
  XLSX.writeFile(wb, filename)
}

const REQUIRED_ISSUE_FIELDS: Array<keyof FieldMapping> = ['issueId', 'type', 'status', 'createdAt', 'updatedAt']

export function App() {
  const [tables, setTables] = useState<LoadedTables>({})
  const [issueMapping, setIssueMapping] = useState<FieldMapping | undefined>()
  const [demandMapping, setDemandMapping] = useState<RefMapping | undefined>()
  const [dtsMapping, setDtsMapping] = useState<RefMapping | undefined>()
  const [showMapping, setShowMapping] = useState(false)
  // Optional issue columns selected by user (fixed columns are: 标签, issue单号, 异常原因)
  const [visibleColumns, setVisibleColumns] = useState<string[] | undefined>(undefined)
  const [filters, setFilters] = useState<{ type?: IssueType[]; status?: IssueStatus[]; label?: TagLabel[]; search?: string }>({})

  const issues = tables.issues?.rows || []
  const labeled = useMemo(() => {
    if (!issues.length || !issueMapping) return []
    try {
      return labelIssues(issues, issueMapping, {
        demand: tables.demand && demandMapping ? { rows: tables.demand.rows, mapping: demandMapping } : undefined,
        dts: tables.dts && dtsMapping ? { rows: tables.dts.rows, mapping: dtsMapping } : undefined
      })
    } catch (e) {
      console.error(e)
      return []
    }
  }, [issues, issueMapping, tables.demand, tables.dts, demandMapping, dtsMapping])

  const filtered = useMemo(() => {
    return labeled.filter((r) => {
      const typeOk = !filters.type || filters.type.length === 0 || filters.type.includes(r[issueMapping!.type] as IssueType)
      const statusOk = !filters.status || filters.status.length === 0 || filters.status.includes(r[issueMapping!.status] as IssueStatus)
      const labelOk = !filters.label || filters.label.length === 0 || filters.label.includes(r.__label__)
      const searchOk = !filters.search || String(r[issueMapping!.issueId]).includes(filters.search)
      return typeOk && statusOk && labelOk && searchOk
    })
  }, [labeled, filters, issueMapping])

  const handleBeforeUpload = async (file: File, kind: 'issues' | 'demand' | 'dts') => {
    try {
      const parsed = await parseWorkbook(file)
      if (!parsed) return Upload.LIST_IGNORE
      const { rows, headers } = parsed
      if (kind === 'issues') {
        const defaultMap = buildDefaultIssueMapping(headers)
        if (!defaultMap) {
          setTables((s) => ({ ...s, issues: { rows, headers } }))
          setIssueMapping(undefined)
          setShowMapping(true)
          void message.warning('未识别必要列，请在“字段映射/列配置”中手动指定（issue单号/类型/状态/创建时间/最近更新时间）')
        } else {
          setTables((s) => ({ ...s, issues: { rows, headers, mapping: defaultMap } }))
          setIssueMapping(defaultMap)
        }
      } else if (kind === 'demand') {
        const defaultMap = buildDefaultRefMapping(headers)
        if (!defaultMap) {
          setTables((s) => ({ ...s, demand: { rows, headers } }))
          setDemandMapping(undefined)
          setShowMapping(true)
          void message.warning('未识别需求表必要列，请在“字段映射/列配置”中手动指定（单号/是否关闭）')
        } else {
          setTables((s) => ({ ...s, demand: { rows, headers, mapping: defaultMap } }))
          setDemandMapping(defaultMap)
        }
      } else if (kind === 'dts') {
        const defaultMap = buildDefaultRefMapping(headers)
        if (!defaultMap) {
          setTables((s) => ({ ...s, dts: { rows, headers } }))
          setDtsMapping(undefined)
          setShowMapping(true)
          void message.warning('未识别DTS表必要列，请在“字段映射/列配置”中手动指定（单号/是否关闭）')
        } else {
          setTables((s) => ({ ...s, dts: { rows, headers, mapping: defaultMap } }))
          setDtsMapping(defaultMap)
        }
      }
      return Upload.LIST_IGNORE
    } catch (e) {
      console.error(e)
      void message.error('文件解析失败，请检查是否为有效的Excel/CSV文件')
      return Upload.LIST_IGNORE
    }
  }

  const columns = useMemo(() => {
    if (!issueMapping) return []
    const getReasonColor = (reason?: string): string => {
      const r = String(reason || '')
      if (!r) return 'default'
      if (r.includes('缺少需求表') || r.includes('缺少DTS表')) return 'geekblue'
      if (r.includes('超时')) return 'orange'
      if (r.includes('无需求单号') || r.includes('无DTS单号')) return 'purple'
      if (r.includes('不存在') || r.includes('未找到')) return 'magenta'
      if (r.includes('已关闭')) return 'red'
      if (r.includes('状态不允许')) return 'volcano'
      return 'default'
    }
    // Fixed columns
    const fixedCols = [
      { title: 'issue单号', dataIndex: issueMapping.issueId, key: issueMapping.issueId, width: 180, fixed: 'left' as const, ellipsis: true, render: (v: any) => <Tooltip title={String(v ?? '')}><span>{String(v ?? '')}</span></Tooltip> },
      { title: '标签', dataIndex: '__label__', key: '__label__', width: 60, fixed: 'left' as const, render: (v: TagLabel) => v === '异常' ? <Tag color="error">异常</Tag> : v === '正常' ? <Tag color="success">正常</Tag> : <Tag>待定</Tag> },
      { title: '异常原因', dataIndex: '__reason__', key: '__reason__', width: 120, fixed: 'left' as const, render: (v: any) => {
        const value = v as unknown
        if (value === undefined || value === null || String(value).trim() === '') return null
        const items: string[] = Array.isArray(value)
          ? (value as string[])
          : String(value).split(/[\n;；]+/).map((s) => s.trim()).filter(Boolean)
        if (!items.length) return null
        if (items.length === 1) {
          const text = items[0]
          return (
            <Tooltip title={text}>
              <Tag color={getReasonColor(text)} style={{ maxWidth: '100%', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                {text}
              </Tag>
            </Tooltip>
          )
        }
        return (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
            {items.map((text, idx) => (
              <Tooltip title={text} key={idx}>
                <Tag color={getReasonColor(text)} style={{ maxWidth: '100%', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                  {text}
                </Tag>
              </Tooltip>
            ))}
          </div>
        )
      } },
    ]

    // Optional columns come only from the issue sheet headers
    const allIssueHeaders = tables.issues?.headers || []
    const optionalAll = allIssueHeaders.filter((h) => h && h !== issueMapping.issueId)

    // Default optional suggestion: 类型/状态/创建时间/最近更新时间/(关联需求单号)/(关联DTS单号)
    const defaultOptionalSet = new Set<string>([
      issueMapping.type,
      issueMapping.status,
      issueMapping.createdAt,
      issueMapping.updatedAt,
      ...(issueMapping.demandId ? [issueMapping.demandId] : []),
      ...(issueMapping.dtsId ? [issueMapping.dtsId] : []),
    ].filter(Boolean) as string[])

    // Heuristic recommended fields if present in headers
    const recommendedCandidates = [
      '标题', '摘要', '主题',
      '负责人', '处理人', '经办人', '提单人',
      '优先级', '严重程度',
      '产品', '项目', '模块'
    ]
    const recommendedSet = new Set(optionalAll.filter((h) => recommendedCandidates.includes(h)))

    const selected = (visibleColumns && visibleColumns.length > 0)
      ? optionalAll.filter((h) => visibleColumns.includes(h))
      : optionalAll.filter((h) => defaultOptionalSet.has(h) || recommendedSet.has(h))

    const optionalCols = selected.map((h) => ({ title: h, dataIndex: h, key: h, width: 180, ellipsis: true, render: (v: any) => <Tooltip title={String(v ?? '')}><span>{String(v ?? '')}</span></Tooltip> }))
    return [...fixedCols, ...optionalCols]
  }, [issueMapping, visibleColumns, tables.issues?.headers])

  const typeOptions: IssueType[] = ['咨询', '需求', 'BUG']
  const statusOptions: IssueStatus[] = ['处理中', '已接纳', '已解决', '待验证', '不接纳']

  const mappingReady = !!(issueMapping && REQUIRED_ISSUE_FIELDS.every((k) => (issueMapping as any)[k]))

  return (
    <ConfigProvider theme={{ token: { colorPrimary: '#1677ff' } }}>
      <AntApp>
        <Layout style={{ minHeight: '100vh' }}>
          <Header style={{ background: '#fff', borderBottom: '1px solid #eee' }}>
            <Flex align="center" justify="space-between">
              <Space>
                <Title level={4} style={{ margin: 0 }}>Issue 状态打标工具</Title>
                <Text type="secondary">按本地时区计算</Text>
              </Space>
              <Space>
                <Button onClick={() => document.getElementById('upload-issues-input')?.click()}>上传 issuereporter</Button>
                <input id="upload-issues-input" type="file" accept=".xlsx,.xls,.csv" style={{ display: 'none' }} onChange={async (e) => {
                  const f = e.target.files?.[0]; if (!f) return; await handleBeforeUpload(f as any, 'issues'); e.currentTarget.value=''
                }} />
                <Button onClick={() => document.getElementById('upload-demand-input')?.click()}>上传 需求表</Button>
                <input id="upload-demand-input" type="file" accept=".xlsx,.xls,.csv" style={{ display: 'none' }} onChange={async (e) => {
                  const f = e.target.files?.[0]; if (!f) return; await handleBeforeUpload(f as any, 'demand'); e.currentTarget.value=''
                }} />
                <Button onClick={() => document.getElementById('upload-dts-input')?.click()}>上传 DTS 表</Button>
                <input id="upload-dts-input" type="file" accept=".xlsx,.xls,.csv" style={{ display: 'none' }} onChange={async (e) => {
                  const f = e.target.files?.[0]; if (!f) return; await handleBeforeUpload(f as any, 'dts'); e.currentTarget.value=''
                }} />

                <Button icon={<SettingOutlined />} onClick={() => setShowMapping(true)}>字段映射/列配置</Button>
                <Button type="primary" icon={<DownloadOutlined />} disabled={!filtered.length || !mappingReady} onClick={() => exportToExcel(filtered, '打标结果.xlsx')}>导出Excel</Button>
              </Space>
            </Flex>
          </Header>
          <Content style={{ padding: 16 }}>
            <Space direction="vertical" style={{ width: '100%' }} size="large">
              {!tables.issues && (
                <Alert type="info" message={
                  <span>请先上传 issuereporter 表（示例：当前目录有 <Text code>export_issue20250808024127.xlsx</Text>）。</span>
                } />
              )}
              {tables.issues && (!tables.demand || !tables.dts) && (
                <Alert type="warning" message="需求/BUG 的已接纳项将暂不打标，请上传需求表与DTS表后自动完成打标。" showIcon />
              )}

              {/* Upload area moved to header buttons; remove bulky draggers */}

              {tables.issues && mappingReady && (
                <>
                  <Space wrap>
                    <Select
                      allowClear
                      placeholder="按类型筛选"
                      mode="multiple"
                      style={{ width: 240 }}
                      options={typeOptions.map((v) => ({ label: v, value: v }))}
                      onChange={(v) => setFilters((s) => ({ ...s, type: (v && (v as IssueType[]).length ? (v as IssueType[]) : undefined) }))}
                    />
                    <Select
                      allowClear
                      placeholder="按状态筛选"
                      mode="multiple"
                      style={{ width: 300 }}
                      options={statusOptions.map((v) => ({ label: v, value: v }))}
                      onChange={(v) => setFilters((s) => ({ ...s, status: (v && (v as IssueStatus[]).length ? (v as IssueStatus[]) : undefined) }))}
                    />
                    <Select
                      allowClear
                      placeholder="按标签筛选"
                      mode="multiple"
                      style={{ width: 220 }}
                      options={[{ label: '正常', value: '正常' }, { label: '异常', value: '异常' }, { label: '待定', value: '待定' }]}
                      onChange={(v) => setFilters((s) => ({ ...s, label: (v && (v as TagLabel[]).length ? (v as TagLabel[]) : undefined) }))}
                    />
                    <Input.Search allowClear placeholder="搜索 issue 单号" style={{ width: 280 }} onSearch={(v) => setFilters((s) => ({ ...s, search: v || undefined }))} />
                  </Space>
                  <Divider style={{ margin: '12px 0' }} />

                  <Table
                    size="small"
                    dataSource={filtered.map((r, idx) => ({ key: idx, ...r }))}
                    columns={columns}
                    pagination={{ pageSize: 20, showSizeChanger: true }}
                    scroll={{ x: 1000 }}
                    sticky
                    rowClassName={() => 'row-compact'}
                  />
                </>
              )}

              <Modal
                title="字段映射 / 列配置"
                open={showMapping}
                onCancel={() => setShowMapping(false)}
                onOk={() => setShowMapping(false)}
              >
                <Space direction="vertical" style={{ width: '100%' }}>
                  {tables.issues && (
                    <>
                      <Title level={5} style={{ marginTop: 0 }}>issuereporter 字段映射</Title>
                      <FieldSelector headers={tables.issues.headers} mapping={issueMapping} onChange={setIssueMapping} />
                      <Divider />
                    </>
                  )}
                  {tables.demand && (
                    <>
                      <Title level={5} style={{ marginTop: 0 }}>需求表 字段映射</Title>
                      <RefFieldSelector headers={tables.demand.headers} mapping={demandMapping} onChange={setDemandMapping} />
                      <Divider />
                    </>
                  )}
                  {tables.dts && (
                    <>
                      <Title level={5} style={{ marginTop: 0 }}>DTS 表 字段映射</Title>
                      <RefFieldSelector headers={tables.dts.headers} mapping={dtsMapping} onChange={setDtsMapping} />
                      <Divider />
                    </>
                  )}

                  {issueMapping && (
                    <>
                      <Title level={5}>列显示配置</Title>
                      <Select
                        mode="multiple"
                        allowClear
                        style={{ width: '100%' }}
                        placeholder="选择需要显示的可选列（固定显示：标签、issue单号、异常原因）。不选则采用推荐列。"
                        value={visibleColumns}
                        onChange={setVisibleColumns as any}
                        options={(tables.issues?.headers || [])
                          .filter((h) => h && h !== issueMapping.issueId)
                          .map((h) => ({ label: h, value: h }))}
                      />
                    </>
                  )}
                </Space>
              </Modal>
            </Space>
          </Content>
        </Layout>
      </AntApp>
    </ConfigProvider>
  )
}

function FieldSelector({ headers, mapping, onChange }: { headers: string[]; mapping?: FieldMapping; onChange: (m?: FieldMapping) => void }) {
  const [local, setLocal] = useState<FieldMapping | undefined>(mapping)
  React.useEffect(() => setLocal(mapping), [mapping])

  const setField = (k: keyof FieldMapping, v: string | undefined) => {
    setLocal((prev) => ({ ...(prev || ({} as FieldMapping)), [k]: v || '' } as FieldMapping))
  }

  const save = () => {
    if (!local) return onChange(undefined)
    for (const k of REQUIRED_ISSUE_FIELDS) {
      if (!local[k]) {
        message.error(`缺少必要字段映射：${k}`)
        return
      }
    }
    onChange(local)
  }

  return (
    <Space direction="vertical" style={{ width: '100%' }}>
      <Flex gap={8} wrap>
        <Select style={{ minWidth: 220 }} placeholder="issue单号" value={local?.issueId} options={headers.map((h) => ({ label: h, value: h }))} onChange={(v) => setField('issueId', v)} />
        <Select style={{ minWidth: 220 }} placeholder="类型" value={local?.type} options={headers.map((h) => ({ label: h, value: h }))} onChange={(v) => setField('type', v)} />
        <Select style={{ minWidth: 220 }} placeholder="状态" value={local?.status} options={headers.map((h) => ({ label: h, value: h }))} onChange={(v) => setField('status', v)} />
        <Select style={{ minWidth: 220 }} placeholder="创建时间" value={local?.createdAt} options={headers.map((h) => ({ label: h, value: h }))} onChange={(v) => setField('createdAt', v)} />
        <Select style={{ minWidth: 220 }} placeholder="最近更新时间" value={local?.updatedAt} options={headers.map((h) => ({ label: h, value: h }))} onChange={(v) => setField('updatedAt', v)} />
        <Select style={{ minWidth: 220 }} placeholder="关联需求单号(可选)" value={local?.demandId} allowClear options={headers.map((h) => ({ label: h, value: h }))} onChange={(v) => setField('demandId', v)} />
        <Select style={{ minWidth: 220 }} placeholder="关联DTS单号(可选)" value={local?.dtsId} allowClear options={headers.map((h) => ({ label: h, value: h }))} onChange={(v) => setField('dtsId', v)} />
      </Flex>
      <Button type="primary" onClick={save}>保存映射</Button>
    </Space>
  )
}

function RefFieldSelector({ headers, mapping, onChange }: { headers: string[]; mapping?: RefMapping; onChange: (m?: RefMapping) => void }) {
  const [local, setLocal] = useState<RefMapping | undefined>(mapping)
  React.useEffect(() => setLocal(mapping), [mapping])

  const setField = (k: keyof RefMapping, v: string | undefined) => {
    setLocal((prev) => ({ ...(prev || ({} as RefMapping)), [k]: v || '' } as RefMapping))
  }

  const save = () => {
    if (!local?.id || !local.closedFlag) {
      message.error('缺少必要字段映射：单号/是否关闭')
      return
    }
    onChange(local)
  }

  return (
    <Space direction="vertical" style={{ width: '100%' }}>
      <Flex gap={8} wrap>
        <Select style={{ minWidth: 220 }} placeholder="单号列" value={local?.id} options={headers.map((h) => ({ label: h, value: h }))} onChange={(v) => setField('id', v)} />
        <Select style={{ minWidth: 220 }} placeholder="是否关闭列" value={local?.closedFlag} options={headers.map((h) => ({ label: h, value: h }))} onChange={(v) => setField('closedFlag', v)} />
        <Input style={{ minWidth: 300 }} placeholder="表示已关闭的取值，逗号分隔（例：已关闭,是,true,1）" value={local?.closedTrueValues} onChange={(e) => setField('closedTrueValues', e.target.value)} />
      </Flex>
      <Button type="primary" onClick={save}>保存映射</Button>
    </Space>
  )
}
